﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Drawing.Drawing2D;
using System.Runtime.InteropServices;

namespace RoketPack.IM
{
    public partial class NotificationForm : Form
    {
        private string m_Message = "";
        private Timer m_Timer = new Timer();

        /// <summary>
        /// Creates a new NotificationForm that shows the specified message.
        /// </summary>
        /// <param name="msg">The text message to show.</param>
        public NotificationForm(string msg)
        {
            InitializeComponent();
            this.m_Message = msg;
            this.m_Timer.Interval = 5000;
            this.m_Timer.Tick += new EventHandler(m_Timer_Tick);
            this.m_Timer.Start();

            if (Aero.Enabled)
            {
                Aero.SetMargins(this, -1, 0, 0, 0);
                Aero.SetWindowThemeAttributes(this, Aero.WTNCA.NODRAWCAPTION | Aero.WTNCA.NODRAWICON | Aero.WTNCA.NOSYSMENU);
            }
            else
            {
                this.FormBorderStyle = FormBorderStyle.None;
                this.BackColor = Color.FromArgb(61, 61, 61);
                this.ForeColor = Color.White;
            }

            this.Invalidate();
        }

        /// <summary>
        /// This event is raised when the close timer ticks over.  It will
        /// close the notification form.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        void m_Timer_Tick(object sender, EventArgs e)
        {
            this.Close();
        }

        /// <summary>
        /// This event is raised when the notification form needs to repaint it's
        /// client area.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The paint event information.</param>
        private void NotificationForm_Paint(object sender, PaintEventArgs e)
        {
            Font f = null;
            try
            {
                f = new Font(new FontFamily("Segoe UI"), 12, GraphicsUnit.Pixel);
            }
            catch (ArgumentException)
            {
                f = new Font(FontFamily.GenericSansSerif, 11.0f, GraphicsUnit.Pixel);
            }
            Color c = this.ForeColor;
            TextFormatFlags tff = TextFormatFlags.Left;

            string s = NotificationForm.WrapString(this.m_Message);
            if (Aero.Enabled)
            {
                try
                {
                    Aero.DrawText(e.Graphics, s, f, new Rectangle(6, 8, this.Width - 16, this.Height - 16), c, tff, Aero.TextStyle.Glowing);
                }
                catch (InvalidOperationException)
                {
                    e.Graphics.DrawString(s, f, new SolidBrush(c), new PointF(6, 8));
                }
            }
            else
            {
                // Draw borders as well.
                e.Graphics.FillRectangle(new SolidBrush(this.BackColor), new Rectangle(0, 0, this.Width, this.Height));
                e.Graphics.FillRectangle(
                    new LinearGradientBrush(
                        new Point(30, 0),
                        new Point(30, this.Height),
                        this.BackColor,
                        Color.FromArgb(
                            this.BackColor.R - 20,
                            this.BackColor.G - 20,
                            this.BackColor.B - 20
                            )
                   ),
                   new Rectangle(0, 0, this.Width, this.Height)
                );
                e.Graphics.FillEllipse(new SolidBrush(Color.DarkRed), new Rectangle(this.Width - 8, -8, 16, 16));
                Pen border = new Pen(new SolidBrush(Color.FromArgb(17, 34, 68)), 3);
                e.Graphics.DrawLine(border, new Point(0, 0), new Point(this.Width, 0));
                e.Graphics.DrawLine(border, new Point(0, 0), new Point(0, this.Height));
                e.Graphics.DrawLine(border, new Point(this.Width, 0), new Point(this.Width, this.Height));

                e.Graphics.DrawString(s, f, new SolidBrush(c), new PointF(6, 8));
            }
        }

        /// <summary>
        /// Updates the position of the notification window to present the collection
        /// of windows as a stack.  Repositions this notification window on the screen
        /// based on the current height position (i).
        /// </summary>
        /// <param name="screen">The screen to display this notification window on.</param>
        /// <param name="i">The current height position.</param>
        /// <returns>The height of this window (added to the current height position by an external loop).</returns>
        internal int UpdatePositioning(Screen screen, int i)
        {
            Size size = new Size();
            Point location = new Point();
            int c = NotificationForm.CountString(this.m_Message);
            int h = c * 12;
            if (c == 1) h = 11;

            if (Aero.Enabled)
                size = new Size(
                    190,
                    h + 40
                );
            else
                size = new Size(
                    this.Width,
                    h + 16
                );


            if (Aero.Enabled)
                location = new Point(
                    screen.WorkingArea.Right - this.Width - 4,
                    screen.WorkingArea.Bottom - this.Height - 4 - i);
            else
                location = new Point(
                    screen.WorkingArea.Right - this.Width,
                    screen.WorkingArea.Bottom - this.Height - i);

            if (this.Location.X != location.X || this.Location.Y != location.Y ||
                this.Width != size.Width || this.Height != size.Height)
                NotificationForm.SetWindowPos(this.Handle.ToInt32(), HWND_TOPMOST,
                    location.X, location.Y, size.Width, size.Height,
                    SWP_NOACTIVATE);

            if (Aero.Enabled)
                return this.Height + 8;
            else
                return this.Height;
        }

        /// <summary>
        /// Wraps the specified single line string with newline characters so that it
        /// will correctly fit in the notification window.
        /// </summary>
        /// <param name="s">The single line string.</param>
        /// <returns>The multiline string.</returns>
        static internal string WrapString(string s)
        {
            const int CHARS = 29;
            int count = 0;
            List<string> lines = new List<string>();
            string buffer = "";

            for (int i = 0; i < s.Length + 1; i += 1)
            {
                if (count == CHARS || i == s.Length)
                {
                    if (i == s.Length || s[i] == ' ')
                    {
                        lines.Add(buffer);
                        buffer = "";
                        count = 0;
                    }
                    else
                    {
                        string rem = buffer.Substring(buffer.LastIndexOf(" "));
                        lines.Add(buffer.Substring(0, buffer.LastIndexOf(" ")));
                        buffer = rem.Trim();
                        count = buffer.Length;
                    }
                }
                else
                {
                    buffer += s[i];
                    count += 1;
                }
            }

            string res = "";
            foreach (string l in lines)
                res += l + "\r\n";
            return res;
        }

        /// <summary>
        /// Measures the number of lines that will be generated by WrapString() for
        /// this single line string.
        /// </summary>
        /// <param name="s">The single line string.</param>
        /// <returns>The number of lines that will be generated.</returns>
        static internal int CountString(string s)
        {
            const int CHARS = 29;
            int count = 0;
            List<string> lines = new List<string>();
            string buffer = "";

            for (int i = 0; i < s.Length + 1; i += 1)
            {
                if (count == CHARS || i == s.Length)
                {
                    if (i == s.Length || s[i] == ' ')
                    {
                        lines.Add(buffer);
                        buffer = "";
                        count = 0;
                    }
                    else
                    {
                        string rem = buffer.Substring(buffer.LastIndexOf(" "));
                        lines.Add(buffer.Substring(0, buffer.LastIndexOf(" ")));
                        buffer = rem.Trim();
                        count = buffer.Length;
                    }
                }
                else
                {
                    buffer += s[i];
                    count += 1;
                }
            }

            return lines.Count;
        }

        /// <summary>
        /// This event is fired when the notification form is resized by either
        /// the user or changing this.Size.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        private void NotificationForm_Resize(object sender, EventArgs e)
        {
            this.Invalidate();
        }

        /// <summary>
        /// This event is fired when the client area is clicked.
        /// </summary>
        /// <param name="sender">The sender of the event.</param>
        /// <param name="e">The event information.</param>
        private void NotificationForm_Click(object sender, EventArgs e)
        {
            this.Close();
        }

        private const int WM_NCLBUTTONDOWN = 0x00A1;

        /// <summary>
        /// This function is called when the window recieves a native window
        /// message.  We use it to handle clicks outside of the client area
        /// and close the form anyway.
        /// </summary>
        /// <param name="m"></param>
        protected override void WndProc(ref System.Windows.Forms.Message m)
        {
            if (m.Msg == WM_NCLBUTTONDOWN)
            {
                this.Close();
            }

            base.WndProc(ref m);
        }

        #region Focus Stealing Prevention
        protected override bool ShowWithoutActivation
        {
            get
            {
                return true;
            }
        }

        private const int SW_SHOWNOACTIVATE = 4;
        private const int HWND_TOPMOST = -1;
        private const uint SWP_NOACTIVATE = 0x0010;

        [DllImport("user32.dll", EntryPoint = "SetWindowPos")]
        static private extern bool SetWindowPos(
             int hWnd,           // window handle
             int hWndInsertAfter,    // placement-order handle
             int X,          // horizontal position
             int Y,          // vertical position
             int cx,         // width
             int cy,         // height
             uint uFlags);       // window positioning flags

        [DllImport("user32.dll")]
        static private extern bool ShowWindow(IntPtr hWnd, int nCmdShow);

        internal void ShowInactiveTopmost()
        {
            NotificationForm.ShowWindow(this.Handle, SW_SHOWNOACTIVATE);
            NotificationForm.SetWindowPos(this.Handle.ToInt32(), HWND_TOPMOST,
                this.Left, this.Top, this.Width, this.Height,
                SWP_NOACTIVATE);
        }

        #endregion
    }
}
